package utils

import (
	"context"
	"deploy/schema"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Status string

var NotStarted Status = "NotStarted"
var Init Status = "Init"
var Running Status = "Running"
var Stoped Status = "Stoped"
var Sucess Status = "Sucess"
var Fail Status = "Fail"

func (s Status) Str() string {
	return fmt.Sprintf("%v", s)
}

type Job struct {
	// 子命令不设置超时了
	TimeOut    int
	CancelFunc context.CancelFunc
	Context    context.Context
	Ok         chan bool
	Fail       chan bool
	Jobs       []SubJob
	File       *os.File // 写输出
	JobId      uint
	Status     Status
	Log        jobLogSlice
}

type SubJob struct {
	Cmd   Command
	JobId string
}

type Command struct {
	CommandFunc func(string) ([]byte, error) `json:"-"` // 转json或者解析时忽略此字段
	CommandName string                       `json:"command_name"`
	CommandArg  string                       `json:"command_arg"`
	Weight      int                          `json:"-"` // 转json或者解析时忽略此字段
}

type Commands []Command

func (cs Commands) Len() int           { return len(cs) }
func (cs Commands) Less(i, j int) bool { return cs[i].Weight < cs[j].Weight }
func (cs Commands) Swap(i, j int)      { cs[i], cs[j] = cs[j], cs[i] }

func (cs Commands) ToString() string {
	result := ""
	for _, c := range cs {
		result = result + c.CommandName + "\n"
	}
	return result
}

func (cs Commands) Remove(index int) Commands {
	return append(cs[:index], cs[index+1:]...)
}

// client把server的消息对象转为local任务
func NewJob(jm *JobMessage) Job {
	jobs := []SubJob{}
	for n, job := range jm.Jobs {
		subjob := SubJob{JobId: fmt.Sprintf("%d-%d-%s", jm.JobId, n, job.CommandName), Cmd: job}
		jobs = append(jobs, subjob)
	}
	j := Job{
		Ok:   make(chan bool, 1),
		Fail: make(chan bool, 1),

		Jobs:    jobs,
		JobId:   jm.JobId,
		TimeOut: jm.Timeout,
	}
	return j
}

type stringFunc map[string]interface{}

var SF = stringFunc{
	//"Echo":Command{Echo,"Echo","",0},
	"DownFile":      Command{DownFile, "DownFile", "", 10},
	"ServerStop":    Command{ServerStop, "ServerStop", "", 20},
	"RpmUninstall":  Command{RpmUninstall, "RpmUninstall", "", 30},
	"RpmInstall":    Command{RpmInstall, "RpmInstall", "", 40},
	"RpmUpdate":     Command{RpmUpdate, "RpmUpdate", "", 50},
	"WriteConf":     Command{WriteConf, "WriteConf", "", 60},
	"ServerStart":   Command{ServerStart, "ServerStart", "", 70},
	"ServerRestart": Command{ServerRestart, "ServerRestart", "", 80},
	"PortCheck":     Command{PortCheck, "PortCheck", "", 90},
}

func (s stringFunc) Funcs() []string {
	result := []string{}
	for k, _ := range s {
		result = append(result, k)
	}
	return result
}

func (s stringFunc) GetFunc(funcName string) interface{} {
	for k, v := range s {
		if k == funcName {
			return v
		}
	}
	return nil
}

func (j *Job) SetCmdFunc() {
	for k, v := range j.Jobs {
		n := v
		isok := true
		switch v.Cmd.CommandName {
		case "RpmInstall":
			n.Cmd.CommandFunc = RpmInstall
		case "RpmUninstall":
			n.Cmd.CommandFunc = RpmUninstall
		case "Sleep":
			n.Cmd.CommandFunc = Sleep
		case "Echo":
			n.Cmd.CommandFunc = Echo
		case "ServerStart":
			n.Cmd.CommandFunc = ServerStart
		case "ServerStop":
			n.Cmd.CommandFunc = ServerStop
		case "ServerRestart":
			n.Cmd.CommandFunc = ServerRestart
		case "DownFile":
			n.Cmd.CommandFunc = DownFile
		case "RpmUpdate":
			n.Cmd.CommandFunc = RpmUpdate
		case "PortCheck":
			n.Cmd.CommandFunc = PortCheck
		case "WriteConf":
			n.Cmd.CommandFunc = WriteConf
		default:
			log.Println("func 异常", v.Cmd.CommandName)
			isok = false
		}
		if isok {
			j.Jobs[k] = n
		}
	}
}

// 任务初始化
func (j *Job) Initing() {
	// 设置默认超时时间
	if j.TimeOut == 0 {
		j.TimeOut = 600
	}
	j.Context, j.CancelFunc = context.WithTimeout(context.Background(),
		time.Second*time.Duration(j.TimeOut))
	j.Status = Running
	j.Log = []jobLog{}
	j.File, _ = OpenFile(fmt.Sprintf("%d", j.JobId))
	j.File.WriteString(fmt.Sprintf("[%d] [init] ok\n", j.JobId))
}

type jobLog struct {
	TimeStr     string `json:"time"`
	CommandName string `json:"command"`
	CommandArg  string `json:"args"`
	Out         string `json:"out"`
	ErrOut      string `json:"err_out"`
	JobId       uint   `json:"job_id"`
	JobIdN      int    `json:"command_id"`
	Status      string `json:"status"`
}

type jobLogSlice []jobLog

func NewJobLog(timeStr, CommandName, args, status string, out []byte, err error, jobId uint, jobIdN int) jobLog {
	if args == "WriteConf" {
		args = "conf.file"
	}
	errlog := ""
	if err != nil {
		errlog = err.Error()
	}
	re := jobLog{
		timeStr, CommandName, args, string(out), errlog, jobId, jobIdN, status,
	}
	return re
}

// 任务执行
func (j *Job) Run() {
	// defer j.File.Close()
	// defer j.CancelFunc()

	j.Status = Running
	go func() {
		defer func() {
			if recover() != nil {
				j.Status = Fail
			}
		}()
		for n, subjob := range j.Jobs {
			subLog := ""
			log.Println("开始执行任务：", n)
			if subjob.Cmd.CommandName == "WriteConf" {
				subLog = subLog + fmt.Sprintf("%s JOBID:%d-%d FUNC:[%s] ARGS:[conf.file]\n",
					Now(), j.JobId, n, subjob.Cmd.CommandName)
			} else {
				subLog = subLog + fmt.Sprintf("%s JOBID:%d-%d FUNC:[%s] ARGS:[%s] \n",
					Now(), j.JobId, n, subjob.Cmd.CommandName, subjob.Cmd.CommandArg)
			}
			log.Println(subjob.Cmd.CommandName, subjob.Cmd.CommandArg)
			logout, logErr := subjob.Cmd.CommandFunc(subjob.Cmd.CommandArg)
			log.Println(string(logout))
			log.Println(logErr)
			status := "ok"
			//subLog = subLog +fmt.Sprintf("%s JOBID:%d-%d FUNC:[%s] out: %s\n", logout)
			//ErrLog:=""
			if logErr != nil {
				status = "err"
				//ErrLog = logErr.Error()
				subLog = subLog + fmt.Sprintf("%s JOBID:%d-%d FUNC:[%s] ERROUT:[%s] STATUS:[err]\n",
					Now(), j.JobId, n, subjob.Cmd.CommandName, logErr.Error())
				j.File.WriteString(subLog)
				subJsonLog := NewJobLog(Now(), subjob.Cmd.CommandName, subjob.Cmd.CommandArg, status, logout, logErr, j.JobId, n)
				j.Log = append(j.Log, subJsonLog)

				j.Fail <- true
				panic(logErr)
			}
			subLog = subLog + fmt.Sprintf("%s JOBID:%d-%d FUNC:[%s]  OUT:[%s] STATUS:[ok] \n",
				Now(), j.JobId, n, subjob.Cmd.CommandName, logout)
			subJsonLog := NewJobLog(Now(), subjob.Cmd.CommandName, subjob.Cmd.CommandArg, status, logout, logErr, j.JobId, n)
			log.Println("sublog:", subLog)
			j.File.WriteString(subLog)
			j.Log = append(j.Log, subJsonLog)

			//log.Println("j.log:",j.Log)
		}
		j.Ok <- true
	}()
	select {
	case <-j.Ok:
		j.Status = Sucess
		j.File.WriteString(Now() + fmt.Sprintf("[%d] job %v\n", j.JobId, j.Status))
		return
	case <-j.Context.Done():
		j.Status = Stoped
		j.File.WriteString(fmt.Sprintf("%s 超时，kill %v\n", Now(), j.Status))
		j.CancelFunc()
		return
	case <-j.Fail:
		j.File.WriteString(fmt.Sprintf("任务出错：%s\n", j.Status))
		return
	}
}

// 执行shell
func RunShell(cmd string) ([]byte, error) {
	out, err := exec.Command("bash", "-c", cmd).CombinedOutput()
	// ch <- true
	return out, err
}

// 写文件
func WiteFile(file string, b []byte) {
	ioutil.WriteFile(file, b, 0x644)
}

// 安装rpm包
func RpmInstall(rpm string) ([]byte, error) {
	return RunShell(fmt.Sprintf("rpm -ivh %s --nodeps", rpm))
}

// 卸载rpm
func RpmUninstall(rpm string) ([]byte, error) {
	RunShell(fmt.Sprintf("rpm -qa | grep %s |xargs rpm -e  --nodeps ", rpm))
	isinstal, err := RunShell(fmt.Sprintf("rpm -qa | grep %s | wc -l ", rpm))
	log.Println(string(isinstal), err)
	n, _ := strconv.Atoi(string(isinstal))
	log.Println("n:", n, "type:", reflect.TypeOf(n))
	if n > 0 {
		return nil, errors.New("卸载失败")
	}
	return []byte("卸载已成功"), nil
}

// 升级rpm
func RpmUpdate(rpm string) ([]byte, error) {
	return RunShell(fmt.Sprintf("rpm -U %s --nodeps", rpm))
}

// test1
func Sleep(t string) ([]byte, error) {
	return RunShell(fmt.Sprintf("sleep %s", t))
}

func Echo(n string) ([]byte, error) {
	return RunShell(fmt.Sprintf("echo %s", n))
}

// 服务控制
func ServerStart(serverName string) ([]byte, error) {
	return RunShell(fmt.Sprintf("supervisorctl start %s", serverName))
}

// 服务控制
func ServerStop(serverName string) ([]byte, error) {
	return RunShell(fmt.Sprintf("supervisorctl stop %s", serverName))
}

// 服务控制
func ServerRestart(serverName string) ([]byte, error) {
	return RunShell(fmt.Sprintf("supervisorctl restart %s", serverName))
}

// 端口检测
func PortCheck(ports string) ([]byte, error) {
	// 增加agent 执行tcp端口检测的延时
	p := strings.Split(ports, ",")
	if len(p) >= 1 {
		var err string
		var ok bool
		for i := 0; i < 10; i++ {
			time.Sleep(3 * time.Second)
			ok, err = TcpPortIsOpen(p)
			if ok {
				return nil, nil
			}
		}
		return []byte(err), errors.New(err)
	}

	return []byte("端口参数异常"), errors.New("端口参数异常：" + ports)
}

// 打开文件
func OpenFile(filename string) (*os.File, error) {
	return os.OpenFile(fmt.Sprintf("./log/%s.log", filename), os.O_CREATE|os.O_RDWR, 0644)
}

// 下载文件
func DownFile(url string) ([]byte, error) {
	urlSlice := strings.Split(url, ",")
	log.Println(urlSlice)
	res, err := http.Get(urlSlice[0])
	if err != nil {
		log.Printf("Get%s Err: %v", urlSlice[0], err)
		res, err = http.Get(urlSlice[1])
		if err != nil {
			log.Printf("Get%s Err: %v", urlSlice[1], err)
			return []byte("open url err "), err
		}
	}
	keys := strings.Split(urlSlice[0], "/")
	filename := keys[len(keys)-1]
	f, err := os.Create(fmt.Sprintf("./download/%s", filename))
	defer f.Close()
	if err != nil {
		return []byte("create file err "), err
	}
	io.Copy(f, res.Body)
	localMd5, _ := Md5sum(f.Name())
	if len(urlSlice) == 3 {
		if urlSlice[2] != "nomd5" {
			if localMd5 == urlSlice[2] {
				return []byte("md5 ok"), nil
			} else {
				return []byte("下载文件的md5值为：" + localMd5), errors.New("预期为：" + urlSlice[2])
			}
		}

	}
	if len(urlSlice) == 2 {
		if urlSlice[1] != "nomd5" {
			if localMd5 == urlSlice[1] {
				return []byte("md5 ok"), nil
			} else {
				return []byte("下载文件的md5值为：" + localMd5), errors.New("预期为：" + urlSlice[1])
			}
		}

	}
	return []byte("OK"), nil
}

type ByModTime []os.FileInfo

func (fis ByModTime) Len() int {
	return len(fis)
}

func (fis ByModTime) Swap(i, j int) {
	fis[i], fis[j] = fis[j], fis[i]
}

func (fis ByModTime) Less(i, j int) bool {
	return fis[i].ModTime().Before(fis[j].ModTime())
}

// 删除掉指定目录的文件 ，只保留最新的N个
func DeleteFile(n int, dir string) ([]byte, error) {
	f, _ := os.Open(dir)
	fis, _ := f.Readdir(-1)

	f.Close()
	sort.Sort(ByModTime(fis))
	if len(fis) <= n {
		return nil, nil
	}
	for _, i := range fis[:len(fis)-n] {
		fullName := path.Join(dir, i.Name())
		_, err := os.Stat(fullName)
		if err != nil {
			continue
		}
		os.Remove(fullName)
		//		fmt.Println()
	}
	return nil, nil
}

// 把配置内容写入文件中
func WriteConf(data string) ([]byte, error) {
	var a []schema.ConfigDataEntity
	err := json.Unmarshal([]byte(data), &a)
	if err != nil {
		return nil, err
	}
	msg := ""
	for _, conf := range a {
		dir := conf.ConfigData.ConfigPath
		if !DirExist(dir) {
			os.Mkdir(dir, 0755)
		}
		filename := path.Join(dir, conf.ConfigData.ConfigName)
		f, err := os.Create(filename)
		if err != nil {
			return nil, err
		}
		n, err := f.WriteString(conf.ConfigData.ViewData)
		if err != nil {
			return nil, err
		}
		f.Close()
		msg = msg + fmt.Sprintf("write conf to %s [%d] ok.\n", filename, n)
	}
	return []byte(msg), nil
}

// 返回配置文件的内容
func GetConfData(s *schema.AgentConf, c chan []byte) {
	for n, conf := range s.ConfList {
		localConf := conf
		filename := path.Join(conf.ConfigPath, conf.ConfigName)
		// 文件存在更新数据
		if isExist(filename) {
			f, _ := os.Open(filename)
			data, err := io.ReadAll(f)
			if err != nil {
				log.Println(err)
				continue
			}
			localConf.ContentData = string(data)
		}
		s.ConfList[n] = localConf
	}
	c <- NewMessage2("12345678", 10, s)
}

// 上传指定文件 待验证
func UploadAFile(server, filename string, c chan []byte) {
	upfile := UploadFileInfoMessage{}
	f, err := os.Stat(filename)
	md5, _ := Md5sum(filename)
	if err != nil {
		log.Println("计算md5值失败", err)

		msg := NewMessage("12345678", 7, ToJson(&upfile))
		c <- msg
		return
	}
	upfile.Name = filename
	upfile.Md5 = md5
	upfile.Size = f.Size()
	upfile.ModTime = f.ModTime()
	log.Printf("上传文件是: %s ,%v\n", filename, upfile)
	// 先回复给server
	msg := NewMessage("12345678", 7, ToJson(&upfile))
	c <- msg
	log.Println("上传文件的回复消息成功")
	// 处理上传任务
	url := fmt.Sprintf("%s/api/rest/ecsdeploy/internal/log/upload?hostname=%s&md5=%s", server, GetHostName(), md5)
	body, err := NewfileUploadRequest(url, "POST", filename)
	log.Printf("上传文件返回数据：%s\n", body)
	if err != nil {
		log.Printf("上传文件错误：%s\n", body)
	}

}

// 上传指定时间内和文件头的文件
func UploadFileByTime(server string, gs *schema.GetLogByTimeSchema, c chan []byte) {
	var tu int64
	loc, _ := time.LoadLocation("Local")
	formatTime, err := time.ParseInLocation("2006-01-02T15:04:05.000Z", gs.Time, loc)
	log.Println(formatTime)
	if err != nil {
		log.Println("err:````", err.Error())
		tu = time.Now().Unix()
	} else {
		tu = formatTime.Unix()
	}
	f := GetFirstFileViaTime(gs.LogDir, gs.LogHead, tu)

	log.Println("查找到的日志文件是：", f)
	if f != nil {
		UploadAFile(server, path.Join(gs.LogDir, f.Name()), c)
	} else {
		upfile := UploadFileInfoMessage{}
		upfile.Name = ""
		upfile.Md5 = ""
		upfile.Size = 0

		// 先回复给server
		msg := NewMessage("12345678", 7, ToJson(&upfile))
		c <- msg
	}

}

//  验证看目录功能ok
func ListDir(dir string, c chan []byte) {
	listdir := FileListMessage{Hostname: GetHostName()}
	defer func() {
		msg := NewMessage("12345678", 6, ToJson(&listdir))
		c <- msg
		log.Println("查看目录的回复消息成功")
	}()
	f := GetFileList(dir)
	if len(f) == 0 {
		return
	}
	for _, i := range f {
		listdir.FileList = append(listdir.FileList,
			FileInfo{filepath.Join(dir, i.Name()), i.ModTime(), i.Size()})
	}
	log.Printf("查看目录:%s 文件列表是：%v\n", dir, listdir)
}

// 部署命令
func Deploy(cmdDesc, cmd string, timeout int) {
	// 创建日志文件
	t := time.Now().Format("2006-01-02--15:04:05")
	f, err := os.OpenFile(fmt.Sprintf("./log/%s.log", cmdDesc), os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		fmt.Println("open log err ,return")
		return
	}
	f.WriteString("start\t" + t + "\n")
	f.WriteString(cmd + "\n")
	// 执行命令
	//ch := make(chan bool,1)
	out, err := RunShell(cmd)
	// 设置超时
	//select {
	//	case <- ch:
	//		fmt.Println("执行成功")
	//	case <-time.After(  time.Second * time.Duration(timeout)):
	//		cancelFunc()
	//		fmt.Println("执行命令超时，退出")
	//		return
	//}
	// 写入日志
	f.Write(out)
	if err != nil {
		f.WriteString(fmt.Sprintf("执行命令出错： %s\n", err.Error()))
	}
	f.WriteString("end\t" + t + "\n")
	// 返回结果

}

func DoJob(message *JobMessage, c chan []byte) {
	// 解析为任务
	job := NewJob(message)
	defer job.File.Close()
	// 初始化任务
	job.Initing()
	job.SetCmdFunc()
	job.Report(c)
	// 任务执行
	job.Run()
	log.Println("结束后是：", job.Status)
	job.Report(c)
	// 任务状态上报

	//清理文件
	DeleteFile(5, "download")
}

func (j *Job) Report(c chan []byte) {
	log.Println(j.Log)
	data, err := json.Marshal(j.Log)
	if err != nil {
		log.Println("convert to json err :", err.Error())
	}
	log.Println(data)
	l := JobResult{
		JobLog:    string(data),
		JobId:     j.JobId,
		JobStatus: fmt.Sprintf("%v", j.Status),
	}
	log.Println(l.JobStatus)
	msg := NewMessage("12345678", 3, ToJson(&l))
	c <- msg
	//log.Println("上报的数据是：",l.JobLog)
}

//  1、server端编写任务上报的接口
// 2、job执行结果上报
//  4、http服务，执行接口
// 5、rpm包上传  下载服务，
//   agent上传下载的功能 。文件完整性校验
//-------
//  6、数据库 增删改查部分
//  7、上报日志入库
// TODO 8、联合运维管理平台进行接口开发。
// 安全类
// ----
// 9.配置中心api

// TODO 10.连通ci/cd 串通打包服务
